// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    internal class CreateTracker<TModel>
        : ModelTracker<TModel>
    {
        public CreateTracker(
            IMapperContext mapperContext,
            IStatefulModel statefulModel,
            Action<ISaveContext> beforeSaveAction,
            Action<ISaveContext> afterSaveAction)
            : this(mapperContext, (TModel)statefulModel.Current,
                    beforeSaveAction, afterSaveAction)
        {
            _statefulModel = statefulModel;

            if (statefulModel.ModelState == ModelState.New)
            {
                _statefulModel.CheckedOut = true;
            }

            if (statefulModel.ModelState != ModelState.New &&
                statefulModel.ModelState != ModelState.NewModified)
            {
                throw new InvalidOperationException("ModelState needs to be New or NewModified, current ModelState: " + statefulModel.ModelState);
            }
        }

        public CreateTracker(
            IMapperContext mapperContext,
            TModel model,
            Action<ISaveContext> beforeSaveAction,
            Action<ISaveContext> afterSaveAction)
            : base(mapperContext, model, beforeSaveAction, afterSaveAction)
        {
            _statefulModel = this.CreateStatefulModel(_model, ModelState.NewModified);

            ((StatefulModel<TModel>)_statefulModel).ApplyDefaultValue(model);
        }

        protected override IStatefulModel StatefulModel => _statefulModel;

        protected override void BuildUpdateableModels(object model)
        {
            var modelMetaParent = ModelMetaManager.GetModelMeta(model.GetType());
            var compositeModelAccessor = _mapperContext.GetCompositeModelAccessor(model.GetType());

            if (_statefulModel != null && _statefulModel.Current == model)
            {
                _updateBuilder.Append(
                            new UpdatableModel<TModel>(
                                    modelMetaParent,
                                    ChangeTrackingStrategy.Snapshot,
                                    modelMetaParent.UpdateWhereStrategy,
                                    _statefulModel,
                                    true,
                                    _beforeSaveAction,
                                    _afterSaveAction));
            }
            else
            {
                _updateBuilder.Append(
                                model.GetType(),
                                compositeModelAccessor,
                                modelMetaParent,
                                model,
                                ModelState.NewModified,
                                true,
                                _beforeSaveAction,
                                _afterSaveAction);
            }

            var properties = modelMetaParent.CascadeCreateProperties;

            foreach (var property in properties)
            {
                var modelType = property.EmbeddedType;
                var modelMeta = ModelMetaManager.GetModelMeta(modelType);
                var childCompositeModelAccessor = _mapperContext.GetCompositeModelAccessor(modelType);

                var value = compositeModelAccessor.GetValue(model, property);

                if (value == null)
                {
                    continue;
                }

                if (property.IsContainerType)
                {
                    var listValue = ((IEnumerable<object>)value).Cast<object>().ToList();
                    foreach (var itemValue in listValue)
                    {
                        if (modelMeta.HasCascadeCreateProperties)
                        {
                            this.BuildUpdateableModels(itemValue);
                        }
                        else
                        {
                            _updateBuilder.Append(modelType,
                                                    childCompositeModelAccessor,
                                                    itemValue,
                                                    ModelState.NewModified);
                        }
                    }
                }
                else
                {
                    if (modelMeta.HasCascadeCreateProperties)
                    {
                        this.BuildUpdateableModels(value);
                    }
                    else
                    {
                        _updateBuilder.Append(modelType,
                                                childCompositeModelAccessor,
                                                value,
                                                ModelState.NewModified);
                    }
                }
            }
        }
    }
}
